-module(robust_bank_client).

-export([start/0, deposit/2, withdraw/2, balance/1]).

-define(P1, 3020).
-define(P2, 3030).

start() ->
    case whereis(rbc) of
        undefined ->
            register(rbc, spawn(fun() -> loop(?P1, ?P2) end));
        _ ->
            already_started
    end.

deposit(Who, X)     -> robust_call({deposit, Who, X}).
withdraw(Who, X)    -> robust_call({withdraw, Who, X}).
balance(Who)        -> robust_call({balance, Who}).

robust_call(C) ->
    case whereis(rbc) of
        undefined ->
            not_started;
        Pid ->
            Pid ! C
    end.

loop(Port1, Port2) ->
    receive
        {deposit, Who, X} ->
            Change = robust_rpc({deposit, Who, X}, Port1, Port2);
        {withdraw, Who, X} ->
            Change = robust_rpc({withdraw, Who, X}, Port1, Port2);
        {balance, Who} ->
            Change = robust_rpc({balance, Who}, Port1, Port2)
    end,
    case Change of
        true    -> loop(Port2, Port1);
        false   -> loop(Port1, Port2)
    end.

robust_rpc(X, Port1, Port2) ->
    Id = make_ref(),
    X1 = {call, Id, X},
    io:format("trying to connect to server ~n"),
    case gen_tcp:connect("localhost", Port1, [binary, {packet, 4}]) of
        {ok, Socket} ->
            io:format("sending to server ~n"),
            gen_tcp:send(Socket, [term_to_binary(X1)]),
            wait_reply1(Socket, Id, X, Port2);
        {error, _} ->
            io:format("cannot connect to server ~n"),
            robust_rpc_try_again(Id, X, Port2)
    end.

wait_reply1(Socket, Id, X, Port) ->
    receive
        {tcp, Socket, Bin} ->
            case binary_to_term(Bin) of
                {ack, Id, Reply} ->
                    io:format("server replied ~n"),
                    B = term_to_binary({delete_tag, Id}),
                    gen_tcp:send(Socket, B),
                    gen_tcp:close(Socket),
                    io:format("server reply ~p~n", [Reply]),
                    false;
                _ ->
                    robust_rpc_try_again(Id, X, Port)
            end;
        {tcp_closed, Socket} ->
            robust_rpc_try_again(Id, X, Port)
    after 10000 ->
        io:format("timeout from server~n"),
        gen_tcp:close(Socket),
        robust_rpc_try_again(Id, X, Port)
    end.

robust_rpc_try_again(Id, X, Port) ->
    io:format("trying to connect bak-server~n"),
    case gen_tcp:connect("localhost", Port, [binary, {packet, 4}]) of
        {ok, Socket} ->
            X1 = {call, Id, X},
            io:format("sending to bak-server~n"),
            gen_tcp:send(Socket, [term_to_binary(X1)]),
            wait_reply2(Socket, Id);
        {error, E} ->
            io:format("cannot connect to bak-server for ~p~n", [E]),
            io:format("error, both_servers_down"),
            false
    end.

wait_reply2(Socket, Id) ->
    receive
        {tcp, Socket, Bin} ->
            case binary_to_term(Bin) of
                {ack, Id, Reply} ->
                    B = term_to_binary({delete_tag, Id}),
                    gen_tcp:send(Socket, B),
                    gen_tcp:close(Socket),
                    io:format("bak-server reply ~p~n", [Reply]);
                0 ->
                    io:format("unexpect reply")
            end,
            true;
        {tcp_closed, Socket} ->
            io:format("error at bak-server"),
            true
    after 10000 ->
        io:format("timeout from bak-server~n"),
        gen_tcp:close(Socket),
        false
    end.
